
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <asm/shdev.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include "ff/ff.h"
#else
#include <mve/cv/cv_global.h>
#include "ff.h"

#if !defined(CONFIG_HAVE_DATBUFS) || defined(CONFIG_NO_DATBUFS)
#error configuration CONFIG_HAVE_DATBUFS must be defined, configuration CONFIG_NO_DATBUFS must be deleted !
#endif
#endif

///////////////////////////////////////////////////////////////////////////////////////////////
// TS_HA
///////////////////////////////////////////////////////////////////////////////////////////////
#define BLOCK (CV_BITBUFS_SIZE/TS_PKTLEN*TS_PKTLEN)

//////////////////////////////////////////////////
typedef struct
{
	struct SESSION *session;
	struct SESSION *session_video, *session_audio;

	session_video_type_t tv;
	session_audio_type_t ta;

	uint32_t tv_valid_size, ta_valid_size;

	long load_size, load_offset, load_szcount;

	int eof;
	psysbuf_t load_buf, loadbuf_video, loadbuf_audio;

#ifdef MEDIA_AV_SYNC
	session_av_sync *sync;
	psysbuf_t load_buf_tsaudio;
	uint64_t a_pos; // audio read position
	uint64_t v_pos; // video read position
	int a_put2xa_cnt;
	int v_put2mali_cnt;
	int a_flag;
	int v_flag;
#endif

	FIL file;

}session_unit_t;

#ifdef MEDIA_AV_SYNC
enum {
	PACKET_GET_NONE,
	PACKET_GETTING,
	PACKET_GET_END
};
#endif

static void hwio_readl_shdev(char le, uint8_t dat[4], uint32_t hwaddr)
{
	u32_t fr;

	memcpy(fr.dat, (void*)hwaddr, 4);

	if (le) {
		dat[0] = fr.dat[0];
		dat[1] = fr.dat[1];
		dat[2] = fr.dat[2];
		dat[3] = fr.dat[3];
	} else {
		dat[0] = fr.dat[3];
		dat[1] = fr.dat[2];
		dat[2] = fr.dat[1];
		dat[3] = fr.dat[0];
	}
}

static void hwio_writel(uint32_t value, phys_addr_t hwaddr)
{
	memcpy((void*)hwaddr, &value, 4);
}

#ifdef MEDIA_AV_SYNC

/**
 * ==== TS packet header(4Bytes) ====
 *  ===============================================================
 * {|sync_byte|trans_err_indi|payload_unit_start_indi|trans_pri|PID|
 * {|8b(0x47) |1b            |1b                     |1b       |13b|
 *  --------------------------------------------------------------
 *  trans_scram_ctl|adapt_field_ctl|playload_flag|contin_cnt|}
 *  2b             |1b             |1b           |4b        |}
 *  ========================================================
 *
 * if 'adaptation_field_control' is 1, have the PCR data after header
 * PCR: 8Bytes + nBytes
 *
 * ==== PES packet(if 'payload_unit_start_indicator' is 1) 8~19Bytes ====
 *  ====================================================================================
 * {|pkt_start_code_prefix|stream_id|PES_pkt_len|'10'|scram_ctl|priority|data_align_indi|
 * {|24bits(0x000001)     |8b       |16b        |2b  |2b       |1b      |1b             |
 *  ------------------------------------------------------------------------------------
 *  copyright|origin_or_copy|PTS_DTS_flags       |ESCR_flag|ES_rate_flag|DSM_trick_mode_flag|
 *  1b       |1b            |2b(01pts, 11pts+dts)|1b       |1b          |1b                 |
 *  ----------------------------------------------------------------------------------------
 *  add_copy_info_flag|PES_CRC_flag|PES_extension_flag|PES_header_data_len|'0011'|PTS[32..30]|
 *  1b                |1b          |1b                |8b                 |4b    |3b         |
 *  ----------------------------------------------------------------------------------------
 *  '1'|PTS[29..15]|'1'|PTS[14..0]|'1'|'0001'|DTS[32..30]|'1'|DTS[29..15]|'1'|DTS[14..0]|'1'|}
 *  1b |15b        |1b |15b       |1b |4b    |3b         |1b |15b        |1b |15b       |1b |}
 *  ========================================================================================
 *
 * The PCR time unit: 27000000 / 300 and 27000000 % 300, PCR / 27000000 = second
 * The PTS/DTS time unit: 90000, pts / 90000 = second
 * @see https://blog.csdn.net/heibao111728/article/details/80308475
 */
long get_packet(session_unit_t *punit, uint8_t chn, psysbuf_t dstbuf, psysbuf_t srcbuf, int ilen, int pktl, int pid, int *flag)
{
	u32_t val32;
	long off = 0;
	int sid, iLoadBeginPosition, m, n;
	unsigned int pes_len;
	phys_addr_t dst = dstbuf->haddr;
	phys_addr_t src = srcbuf->haddr + srcbuf->offset;
	int len = ilen;
	static int32_t a_pts_last = 0, v_pts_last = 0;

	while (len-- > 0) { // the num of TS packets
		/* parse TS packet header(4Bytes) */
		hwio_readl_shdev(1, val32.dat, src);
		sid = ((val32.dat[1] & 0x1F) << 8) | val32.dat[2]; // PID of ts pkt header: 13bits
		if (sid == pid) { // parse needful audio or video packets, skip PAT/PMT/SDT and other audio video packets
			iLoadBeginPosition = -1;
			m = ((val32.dat[3] >> 4) & 0x03); // adaptation_field_control(1bit) + playload_flag(1bit)
			n = ((val32.dat[1] >> 6) & 0x01); // payload_unit_start_indicator: 1bit
			if (m == 1) // skip 4bytes TS header
				iLoadBeginPosition = 4;
			else if (m == 3) { // skip 4bytes TS header + nBytes PCR
				hwio_readl_shdev(1, val32.dat, src + 4);
				iLoadBeginPosition = 5 + val32.dat[0];
			}

			/* parse PES */
			// if payload_unit_start_indicator is 1
			if (n && iLoadBeginPosition > 0) {
				iLoadBeginPosition += 7;

				uint8_t pts_dts_flag;
				memcpy(&pts_dts_flag, (void *)src + iLoadBeginPosition, sizeof(uint8_t));
				pts_dts_flag >>= 6; // 10pts 01dts 11pts+dts
				iLoadBeginPosition += 1;

				m = iLoadBeginPosition & 0x3;
				n = iLoadBeginPosition & 0xfc;
				hwio_readl_shdev(1, val32.dat, src + (long)(n));
				iLoadBeginPosition += 1;

				int32_t pts = 0, dts = 0;
				uint32_t tmp1, tmp2;
				if (pts_dts_flag == 0x02) { //&& val32.dat[m] == 5
					/* pts */
					tmp1 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition, 1);
					pts |= (((tmp1 & 0x0E) >> 1) << 30); // PTS[32..30]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 1, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 2, 1);
					tmp2 |= tmp1;
					pts |= ((tmp2 >> 1) << 15); // PTS[29..15]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 3, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 4, 1);
					tmp2 |= tmp1;
					pts |= (tmp2 >> 1); // PTS[14..0]
					if (pid == punit->tv.id) {
//						debug("video pts: %dms\n", pts / 90);
						punit->sync->v_pkt_duration = (int)pts / 90 - v_pts_last;
						v_pts_last = pts / 90;
					} else {
//						debug("\t\t\t\taudio pts: %dms\n", pts / 90);
						punit->sync->a_pkt_duration = (int)pts / 90 - a_pts_last;
						a_pts_last = pts / 90;
					}
				} else if (pts_dts_flag == 0x03) { //&& val32.dat[m] == 10
					/* pts */
					tmp1 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition, 1);
					pts |= (((tmp1 & 0x0E) >> 1) << 30); // PTS[32..30]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 1, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 2, 1);
					tmp2 |= tmp1;
					pts |= ((tmp2 >> 1) << 15); // PTS[29..15]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 3, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 4, 1);
					tmp2 |= tmp1;
					pts |= (tmp2 >> 1); // PTS[14..0]

					/* dts */
					tmp1 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 5, 1);
					dts |= (((tmp1 & 0x0E) >> 1) << 30); // DTS[32..30]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 6, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 7, 1);
					tmp2 |= tmp1;
					dts |= ((tmp2 >> 1) << 15); // DTS[29..15]

					tmp1 = tmp2 = 0;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 8, 1);
					tmp2 = tmp1 << 8;
					memcpy(&tmp1, (void *)src + iLoadBeginPosition + 9, 1);
					tmp2 |= tmp1;
					dts |= (tmp2 >> 1); // DTS[14..0]
					if (pid == punit->tv.id) {
//						debug("video pts: %dms, dts: %dms\n", pts / 90, dts / 90);
						// 90/ms, 90000/s
						punit->sync->v_pkt_duration = dts / 90 - v_pts_last;
						v_pts_last = dts / 90;
					} else {
//						debug("\t\t\t\taudio pts: %dms, dts: %dms\n", pts / 90, dts / 90);
						punit->sync->a_pkt_duration = dts / 90 - a_pts_last;
						a_pts_last = dts / 90;
					}
				} else {
					debug("pts dts flag: 0x%02x error!\n", pts_dts_flag);
//					*flag = PACKET_GETTING;
				}
				if (pts_dts_flag) {
					if (*flag != PACKET_GET_NONE)
						*flag = PACKET_GET_END;
					dstbuf->flags |= SYS_BUF_FLAG_BOT_FIRST;
					if (dts)
						dstbuf->user.nLowPart = dts; //TODO: get the time_scale and time_base
					else
						dstbuf->user.nLowPart = pts;
					if (pid == punit->tv.id) {
						punit->v_put2mali_cnt++;
						dstbuf->user.nHighPart = punit->v_put2mali_cnt;
						//punit->sync->v_real_duration = (punit->sync->v_pkt_duration * punit->sync->a_real_rate)
						//					/ punit->fl->ta.sample_rate;

					} else {
						punit->a_put2xa_cnt++;
						dstbuf->user.nHighPart = punit->a_put2xa_cnt;
					}
				}

				iLoadBeginPosition += val32.dat[m]; // skip pes data, val32.dat[m] is PES_header_data_len
			}

			if (*flag != PACKET_GET_END) {
				if (iLoadBeginPosition > 0 && iLoadBeginPosition < pktl) {
					n = pktl - iLoadBeginPosition;
#ifdef MEDIA_USE_EDMA
					edma_copy(chn, dst + off, src + (long)(iLoadBeginPosition), (uint32_t)(n));
#else
					memcpy(dst + off, src + (long)(iLoadBeginPosition), (uint32_t)(n));
					if (*flag != PACKET_GET_END)
						*flag = PACKET_GETTING;
#endif
					off += (long)(n);
				}
				dstbuf->size = off;
			}
		}
		if (*flag == PACKET_GET_END) {
			*flag = PACKET_GET_NONE;
			break;
		}

		src += (long)pktl;
		srcbuf->offset += pktl;
	}

	dstbuf->size = off;
	return off;
}

static long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	long l2 = BLOCK, ret = 0;
	psysbuf_t buf;

video:
	///////////////////////////////////////////////////////////
	if (punit->session_video != NULL) {
		if (punit->session_video->state != SSTATE_RUNNING_IDLE)
			goto audio;
		buf = punit->loadbuf_video;
		if (buf == NULL) {
			buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
			if (buf != NULL)
				punit->loadbuf_video = buf;
			else
				return -1;
		}
	}

	///////////////////////////////////////////////////////////
	if (punit->load_buf == NULL) {
		punit->load_buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
		if (punit->load_buf == NULL)
			return -1;
	}

	if (punit->load_size > 0 && (punit->load_buf->size == 0 || (punit->load_buf->size > 0
			&& punit->load_buf->offset >= punit->load_buf->size))) {
		l2 = BLOCK;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			punit->load_buf->flags |= SYS_BUF_FLAG_EOF;
		}

		f_lseek(&punit->file, punit->v_pos);
		f_read(&(punit->file), (void*)HWADDR(punit->load_buf->haddr), l2, NULL);
		punit->load_buf->size = l2;
		punit->load_buf->offset = 0;
		punit->load_offset += l2;
		punit->load_size -= l2;
		punit->v_pos = f_tell(&punit->file);
	}

	if (punit->load_buf->flags & SYS_BUF_FLAG_EOF) {
		punit->eof = 1;
		ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
		punit->session_video = NULL;
		if (punit->session_audio != NULL) {
			ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
			punit->session_audio = NULL;
		}
	}

	////////////////////////////////////////////////////////////////////////
	if (punit->session_video != NULL && punit->loadbuf_video != NULL) {
		buf = punit->loadbuf_video;
		ret = get_packet(punit, EDMA_CHN_MEDIA, buf, punit->load_buf,
			(punit->load_buf->size - punit->load_buf->offset) / TS_PKTLEN,
			TS_PKTLEN, punit->tv.id, &punit->v_flag);
		if(ret > 0) {
			if (punit->load_buf->flags & SYS_BUF_FLAG_EOF)
				buf->flags |= SYS_BUF_FLAG_EOF;
			//if (buf->flags & SYS_BUF_FLAG_EOF) {
				punit->loadbuf_video = NULL;
				SessionBufferPush(punit->session_video, buf);
			//}
		}
	}

audio:
	if (punit->session_audio != NULL) {
		if (punit->session_audio->state != SSTATE_RUNNING_IDLE)
			return 0;
		buf = punit->loadbuf_audio;
		if (buf == NULL) {
			buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
			if (buf != NULL) {
				punit->loadbuf_audio = buf;
			}
			else
				return -1;
		}
	}

	///////////////////////////////////////////////////////////
	if (punit->load_buf_tsaudio == NULL) {
		punit->load_buf_tsaudio = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (punit->load_buf_tsaudio == NULL)
			return -1;
	}

	if (punit->load_size > 0 && (punit->load_buf_tsaudio->size == 0
			|| (punit->load_buf_tsaudio->size > 0
			&& punit->load_buf_tsaudio->offset >= punit->load_buf_tsaudio->size))) {
		if (punit->a_pos == (punit->v_pos - l2)) {
#ifdef MEDIA_USE_EDMA
			edma_copy(EDMA_CHN_MEDIA, punit->load_buf_tsaudio->haddr, punit->load_buf->haddr, l2);
#else
			memcpy(punit->load_buf_tsaudio->haddr, punit->load_buf->haddr, l2);
#endif
			punit->load_buf_tsaudio->size = l2;
			punit->load_buf_tsaudio->offset = 0;
			punit->a_pos += l2;
		} else {
			l2 = BLOCK;
			if (l2 >= punit->load_size) {
				l2 = punit->load_size;
				punit->load_buf_tsaudio->flags |= SYS_BUF_FLAG_EOF;
			}

			f_lseek(&punit->file, punit->a_pos);
			f_read(&(punit->file), (void*)HWADDR(punit->load_buf_tsaudio->haddr), l2, NULL);
			punit->load_buf_tsaudio->size = l2;
			punit->load_buf_tsaudio->offset = 0;
			if (punit->session_video == NULL) {
				punit->load_offset += l2;
				punit->load_size -= l2;
			}
			punit->a_pos = f_tell(&punit->file);
		}
	}

	if (punit->load_buf_tsaudio->flags & SYS_BUF_FLAG_EOF) {
		punit->eof = 1;
		ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
		punit->session_audio = NULL;
		if (punit->session_video != NULL) {
			ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
			punit->session_video = NULL;
		}
	}

	/////////////////////////////////////////////////////////////////////////////
	if (punit->session_audio != NULL && punit->loadbuf_audio != NULL) {
		buf = punit->loadbuf_audio;
		ret = get_packet(punit, EDMA_CHN_MEDIA, buf, punit->load_buf_tsaudio,
			(punit->load_buf_tsaudio->size - punit->load_buf_tsaudio->offset) / TS_PKTLEN,
			TS_PKTLEN, punit->ta.id, &punit->a_flag);
		if (ret > 0) {
			if (punit->load_buf_tsaudio->flags & SYS_BUF_FLAG_EOF)
				buf->flags |= SYS_BUF_FLAG_EOF;
			//if (buf->flags & SYS_BUF_FLAG_EOF) {
				punit->loadbuf_audio = NULL;
				SessionBufferPush(punit->session_audio, buf);
			//}
		}
	}

	if (punit->load_buf)
		return punit->load_buf->size;
	else
		return punit->load_buf_tsaudio->size;
}

#else /* MEDIA_AV_SYNC */

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#define shdev_readl(dat,hwaddr)    hwio_readl_shdev(1,dat,hwaddr)
#define shdev_writel(dat,hwaddr)   hwio_writel_shdev(1,dat,hwaddr)
#define shdev_DF(val32)            val32.dat[3]
#define shdev_DH(val32)            val32.dat[2]
#define shdev_DM(val32)            val32.dat[1]
#define shdev_DL(val32)            val32.dat[0]
#define shdev_readb(dat,hwaddr)    hwio_readb_shdev(1,dat,hwaddr)
#define shdev_writeb(dat,hwaddr)   hwio_writeb_shdev(1,dat,hwaddr)
#endif

long edma_tsd_soft(uint8_t chn, phys_addr_t dst, phys_addr_t src, uint32_t len, int pktl, int pid)
{
	u32_t val32;
	long off = 0;
	int sid, iLoadBeginPosition, m, n;

	while (len-- > 0)
	{
		shdev_readl(val32.dat, src);
		sid = ((shdev_DM(val32) & 0x1F) << 8) | shdev_DH(val32);
		if (sid == pid) {
			iLoadBeginPosition = -1;

			m = ((shdev_DF(val32) >> 4) & 0x03);
			n = ((shdev_DM(val32) >> 6) & 0x01);
			if (m == 1)
				iLoadBeginPosition = 4;
			else if (m == 3) {
				shdev_readl(val32.dat, src + 4);
				iLoadBeginPosition = 5 + shdev_DL(val32);
			}

			// payload_unit_start_indicator
			if (n && iLoadBeginPosition > 0)
			{
				iLoadBeginPosition += 8;
				m = iLoadBeginPosition & 0x3;
				n = iLoadBeginPosition & 0xfc;
				shdev_readl(val32.dat, src + (long)(n));
				iLoadBeginPosition = iLoadBeginPosition + val32.dat[m] + 1;
			}

			if (iLoadBeginPosition > 0 && iLoadBeginPosition < pktl) {
				n = pktl - iLoadBeginPosition;
#ifdef XA_USE_EDMA_IOBUF
				edma_copy(chn, dst + off, src + (long)(iLoadBeginPosition), (uint32_t)(n));
#else
				memcpy((void*)(dst + off),(void*)(src + (long)(iLoadBeginPosition)), (uint32_t)(n));
#endif
				off += (long)(n);
			}

		}
		src += (long)pktl;
	}

	return off;

}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#define EDMA_TSD_RUN edma_tsd_soft
#else
#if 1
#define EDMA_TSD_RUN edma_tsd
#else
#define EDMA_TSD_RUN edma_tsd_soft
#endif
#endif
static long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	uint8_t *ldat;
	u32_t vals, vale;
	long l2, off, ret = 0;
	psysbuf_t load_buf, buf;

	///////////////////////////////////////////////////////////
	if (punit->session_video != NULL) {
		if (punit->session_video->state != SSTATE_RUNNING_IDLE)
			return 0;
		buf = punit->loadbuf_video;
		if (buf == NULL) {
			buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
			if (buf != NULL)
				punit->loadbuf_video = buf;
			else
				return -1;
		}
	}

	if (punit->session_audio != NULL) {
		if (punit->session_audio->state != SSTATE_RUNNING_IDLE)
			return 0;
		buf = punit->loadbuf_audio;
		if (buf == NULL) {
			buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
			if (buf != NULL) {
				punit->loadbuf_audio = buf;
			}
			else
				return -1;
		}
	}

	///////////////////////////////////////////////////////////
	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	if (punit->load_size > 0)
	{
		l2 = BLOCK;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			load_buf->flags |= SYS_BUF_FLAG_EOF;
		}

		f_read(&(punit->file), (void*)HWADDR(load_buf->haddr), l2, NULL);
		load_buf->offset = 0;
		load_buf->size = l2;
		punit->load_offset += l2;
		punit->load_size -= l2;

		l2 = punit->load_offset - punit->load_szcount;
		if ((l2 > punit->load_size / 100L) || (l2 > 0x40000)) {
			punit->load_szcount = punit->load_offset;
			debug("\t\tProcess : %f%%, 0x%lx (%ld) bytes\n",
				100.0f*(float)punit->load_offset/(float)(punit->load_offset+punit->load_size),
				punit->load_offset, punit->load_offset
				);
		}

	}
	else
	{
		psysbuf_t buf = SessionBufferPop(punit->session);
		if (buf) {
			if (buf->list.group == load_buf->list.group) {
				load_buf = buf;
			}
			else {
				phys_addr_t src, dst;
				src = buf->haddr + buf->offset;
				dst = load_buf->haddr + load_buf->offset;
#ifdef XA_USE_EDMA_IOBUF
				edma_copy(EDMA_CHN_IO, dst, src, buf->size);
#else
				memcpy((void*)dst, (void*)src, buf->size);
#endif
				load_buf->size = buf->size;
				if (buf->flags & SYS_BUF_FLAG_EOF)
					load_buf->flags |= SYS_BUF_FLAG_EOF;
				load_buf->user = buf->user;
				sysbuf_free(buf);
			}
		}
		else {
			load_buf = NULL;
		}
	}

	if (load_buf == NULL)
		return 0;

	if (load_buf->flags & SYS_BUF_FLAG_EOF) {
		punit->eof = 1;
	}

	////////////////////////////////////////////////////////////////////////
	off = 0;
	l2 = load_buf->size;
	while(off < l2) {
		shdev_readl(vals.dat, load_buf->haddr+load_buf->offset+off);
		if (l2 - off >= TS_PKTLEN)
			shdev_readl(vale.dat, load_buf->haddr+load_buf->offset+off+TS_PKTLEN);
		else
			shdev_DL(vale) = 0x47;
		if (shdev_DL(vals) == 0x47 && shdev_DL(vale) == 0x47)
			break;
		off += 4;
	}

	if(off >= l2) {
		off = (punit->load_offset - l2);
		debug("\t\tProcess : no valid TS data @ 0x%lx (%ld) - 0x%lx (%ld)\n",
		      off, off, punit->load_offset, punit->load_offset);
		load_buf->size = 0;
		goto StreamLoadDone;
	} else if (off > 0) {
		debug("\t\tProcess : Shift TS 0x%lx (%ld) @ 0x%lx (%ld)\n",
		      off, off, punit->load_offset - l2, punit->load_offset - l2);
	}

	load_buf->offset += off;
	load_buf->size -= off;

	if (load_buf == punit->load_buf) {
		off %= TS_PKTLEN;
		if (off > 0 && punit->load_size > off) {
			punit->load_offset += off;
			punit->load_size -= off;
			f_lseek(&(punit->file), punit->load_offset);
		}
	}

	////////////////////////////////////////////////////////////////////////
	if (punit->session_video != NULL && punit->loadbuf_video != NULL) {
		buf = punit->loadbuf_video;

		ret = EDMA_TSD_RUN(EDMA_CHN_MEDIA,
			buf->haddr + buf->size, (phys_addr_t)(load_buf->haddr + load_buf->offset),
			l2 / TS_PKTLEN, TS_PKTLEN, punit->tv.id);
		if (ret <= 0 && (load_buf->flags & SYS_BUF_FLAG_EOF) && buf->size <= 0) {
			hwio_writel(0, buf->haddr + buf->size);
			ret = 4;
		}
		if (ret > 0) {
			buf->size += ret;
		}

		if (buf->size > 0) {
			if (load_buf->flags & SYS_BUF_FLAG_EOF)
				buf->flags |= SYS_BUF_FLAG_EOF;
			if ((buf->flags & SYS_BUF_FLAG_EOF) || (buf->size >= punit->tv_valid_size)) {
				punit->loadbuf_video = NULL;
				SessionBufferPush(punit->session_video, buf);
			}
		}

	}

	/////////////////////////////////////////////////////////////////////////////
	if (punit->session_audio != NULL && punit->loadbuf_audio != NULL) {
		buf = punit->loadbuf_audio;

		ret = EDMA_TSD_RUN(EDMA_CHN_MEDIA,
			buf->haddr + buf->size, (phys_addr_t)(load_buf->haddr + load_buf->offset),
			l2 / TS_PKTLEN, TS_PKTLEN, punit->ta.id);
		if (ret <= 0 && (load_buf->flags & SYS_BUF_FLAG_EOF) && buf->size <= 0) {
			hwio_writel(0, buf->haddr + buf->size);
			ret = 4;
		}
		if (ret > 0) {
			buf->size += ret;
		}

		if (buf->size > 0) {
			if (load_buf->flags & SYS_BUF_FLAG_EOF)
				buf->flags |= SYS_BUF_FLAG_EOF;
			if ((buf->flags & SYS_BUF_FLAG_EOF) || (buf->size >= punit->ta_valid_size)) {
				punit->loadbuf_audio = NULL;
				SessionBufferPush(punit->session_audio, buf);
			}
		}
	}

	// free new buffer
StreamLoadDone: // free new buffer
	l2 = load_buf->size;
	if (load_buf != punit->load_buf)
		sysbuf_free(load_buf);

	//
	return l2;
}


#endif /* MEDIA_AV_SYNC */

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	long res = 0;
	int bstop = 0;

	SESSIONSTATE state;

	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

#ifdef MEDIA_AV_SYNC
	media_flush_time(&punit->sync->sys_time, NULL);
#endif

	if (session->state != SSTATE_RUNNING
		&& session->state != SSTATE_RUNNING_IDLE)
		return session->state;

	res = SESSIONAPI(StreamLoad)(punit);

	if (punit->session_video != NULL) {
		state = punit->session_video->SessionRun(punit->session_video);
		if (punit->eof && state == SSTATE_STOP) {
			bstop |= 1;
		}
	}

	if (punit->session_audio != NULL) {
		state = punit->session_audio->SessionRun(punit->session_audio);
		if (punit->eof && state == SSTATE_STOP) {
			bstop |= 2;
		}
	}

	if ((bstop & 3) == 3) {
		session->state = SSTATE_STOP;
	}

	return session->state;
}

#ifdef MEDIA_AV_SYNC
static
int sync_init(session_unit_t *punit, session_file_t *file)
{
	punit->sync = file->sync;
	memset(punit->sync, 0, sizeof(session_av_sync));
	punit->sync->v_last_play_timestamp.last_tick = xthal_get_ccount();
	punit->sync->a_play_buf_flush_timestamp.last_tick = xthal_get_ccount();
	punit->sync->sys_time.last_tick = xthal_get_ccount();
	punit->sync->a_real_rate = file->ta.sample_rate; // default value, modify in i2sout module
	punit->sync->v_dts_time_base = 90000; // 90/ms, 90000/s
	punit->sync->a_dts_time_base = 90000;
	punit->sync->v_pkt_duration = 40;
	punit->sync->v_real_duration = punit->sync->v_pkt_duration;
	debug("video pakcet duration: %fms\n", punit->sync->v_pkt_duration);
	punit->sync->a_pkt_duration = 23;
	punit->sync->v_last_decode_time = (float)punit->sync->v_pkt_duration / 1000000;

	return 0;
}
#endif

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	int res;
	uint32_t addr;
	sysbuf_group_t gp;
	FRESULT fresult;
	session_file_t fl;

	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_INITED)
		return -EPERM;

	/* Initial loading */
	punit->loadbuf_video = NULL;
	punit->loadbuf_audio = NULL;
	punit->load_size = 0;
	punit->load_offset = 0;
	punit->load_szcount = 0;
	punit->eof = 0;
#ifdef MEDIA_AV_SYNC
	sync_init(punit, file);
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	file->tv.width = file->tv.width;
	file->tv.height = file->tv.height;
#endif

	punit->tv = file->tv;
	punit->session_video = SessionGet(punit->tv.type);
	if (punit->session_video != NULL) {
		fl.type = punit->tv.type;
		fl.ftype = file->ftype;
		fl.tv = file->tv;
		fl.ta = file->ta;
		fl.filename = NULL;
#ifdef MEDIA_AV_SYNC
		fl.sync = file->sync;
		fl.sync->a_real_rate = fl.ta.sample_rate;
#endif
		res = SessionCommand(punit->session_video, SSCMD_STREAM_START, &fl);
		if (res)
		{
			punit->session_video = NULL;
			return res;
		}
		punit->tv_valid_size = 0;
		res = SessionCommand(punit->session_video, SSCMD_STREAM_GETVALIDINSIZE, &punit->ta_valid_size);
		if (res)
		{
		}
	}

	punit->ta = file->ta;
	punit->session_audio = SessionGet(punit->ta.type);
	if (punit->session_audio != NULL) {
		fl.type = punit->ta.type;
		fl.ftype = file->ftype;
		fl.tv = file->tv;
		fl.ta = file->ta;
		fl.filename = NULL;
#ifdef MEDIA_AV_SYNC
		fl.sync = file->sync;
		fl.sync->a_real_rate = fl.ta.sample_rate;
#endif
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_START, &fl);
		if (res)
		{
			punit->session_audio = NULL;
			res = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
			return res;
		}
#ifdef MEDIA_AV_SYNC
		punit->sync->v_real_duration = (punit->sync->v_pkt_duration * punit->sync->a_real_rate)
												/ file->ta.sample_rate;
#endif
		punit->ta_valid_size = 0;
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_GETVALIDINSIZE, &punit->ta_valid_size);
		if (res)
		{
		}
	}

	if (file->filename) {
		if (f_open(&(punit->file), file->filename, FA_READ) != FR_OK)
		{
			debug("open stream file error \n");
			if (punit->session_video)
				res = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
			if (punit->session_audio)
				res = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
			return -EBADF;
		}
		else
		{
			punit->load_size = f_size(&(punit->file));
#ifdef MEDIA_AV_SYNC
			punit->a_pos = f_tell(&punit->file);
			punit->v_pos = f_tell(&punit->file);
#endif
		}
	}

	SessionBufferDeInit(session);

	session->state = SSTATE_RUNNING;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	int res;
	SESSIONSTATE state;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_RUNNING)
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;

	if (punit->load_buf)
		sysbuf_free(punit->load_buf);
	punit->load_buf = NULL;
#ifdef MEDIA_AV_SYNC
	if(punit->load_buf_tsaudio)
		sysbuf_free(punit->load_buf_tsaudio);
	punit->load_buf_tsaudio = NULL;
#endif

	if (punit->session_video != NULL) {
		res = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
		if (res)
		{
		}
		punit->session_video = NULL;
	}

	if (punit->loadbuf_video)
		sysbuf_free(punit->loadbuf_video);
	punit->loadbuf_video = NULL;

	if (punit->session_audio != NULL) {
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
		if (res)
		{
		}
		punit->session_audio = NULL;
	}

	if (punit->loadbuf_audio)
		sysbuf_free(punit->loadbuf_audio);
	punit->loadbuf_audio = NULL;

	f_close(&punit->file);

	SessionBufferDeInit(session);

	// set state
	session->state = SSTATE_INITED;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	if (psz) {
		*psz = TS_PKTLEN;
	}

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (session == NULL || punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
		if (session->state == SSTATE_RUNNING
			|| session->state == SSTATE_RUNNING_IDLE)
			session->state = SSTATE_RUNNING_PAUSE;
	}break;
	case SSCMD_STREAM_RESUME:
	{
		debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
		if (session->state == SSTATE_RUNNING_PAUSE)
			session->state = SSTATE_RUNNING;
	}break;
	case SSCMD_STREAM_STOP:
	{
		debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
	}break;
	case SSCMD_STREAM_GETVALIDINSIZE:
	{
		return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t *)params);
	}break;
	case SSCMD_STREAM_SKIP:
	{
		//return SESSIONAPI(SSCMD_STREAM_SKIP)(session, (int *)params);
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	else
		return;

	debug("SessionDeInit : %s ..\n", session->name);

	SessionBufferDeInit(session);

	if (punit != NULL) {
		free(punit);
	}

	session->handle = NULL;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	session_unit_t *punit = (session_unit_t *)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_tsha =
{ "tsha", (1 << SST_TS),
&SESSIONAPI(SessionInit),
};
